﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.Administration;
using System.Xml;
using System.IO;

namespace Winwise.SPWebConfig.Core.XPathXmlModification
{
    public enum XPathXmlWebConfigModificationType
    {
        AddNode,
        ModifyAttribute
    }

    public enum XPathXmlInsertPosition
    {
        Last,
        First,
        Before,
        After
    }

    public class XPathXmlWebConfigModification : WebConfigModification
    {
        [Persisted]
        private string _ParentXPath;

        [Persisted]
        private string _RelativeXPath;

        [Persisted]
        private string _RelativeSiblingXPath;

        public string RelativeSiblingXPath
        {
            get { return _RelativeSiblingXPath; }
            set { _RelativeSiblingXPath = value; }
        }

        [Persisted]
        private XPathXmlWebConfigModificationType _ModificationType;

        public XPathXmlWebConfigModificationType ModificationType
        {
            get { return _ModificationType; }
            set { _ModificationType = value; }
        }

        [Persisted]
        private XPathXmlInsertPosition _XPathInsertPosition;

        [Persisted]
        private string _Value;

        public string Value
        {
            get { return _Value; }
            set { _Value = value; }
        }

        [Persisted]
        private string _OldAttValue;

        private string OldAttValue
        {
            get { return _OldAttValue; }
            set { _OldAttValue = value; }
        }

        //[Persisted]
        //private bool _CreateParentNodeIfNecessary;

        [Persisted]
        private bool _EraseNodeOnRemove;

        public bool EraseNodeOnRemove
        {
            get { return _EraseNodeOnRemove; }
            set { _EraseNodeOnRemove = value; }
        }



        public string ParentXPath
        {
            get { return _ParentXPath; }
            set { _ParentXPath = value; }
        }

        public string RelativeXPath
        {
            get { return _RelativeXPath; }
            set { _RelativeXPath = value; }
        }

        public XPathXmlInsertPosition XPathInsertPosition
        {
            get { return _XPathInsertPosition; }
            set { _XPathInsertPosition = value; }
        }

        public XPathXmlWebConfigModification()
        {

        }

        public override void ApplyModification(System.Xml.XmlDocument doc, Microsoft.SharePoint.Administration.SPUrlZone zone, Microsoft.SharePoint.Administration.SPWebApplication webapp)
        {
            XmlNode parentNode = doc.SelectSingleNode(ParentXPath);

            if (parentNode == null)
            {
                throw new Exception(string.Format("Cannot find XMLNode for path {0}", ParentXPath));
            }


            switch (ModificationType)
            {
                case XPathXmlWebConfigModificationType.ModifyAttribute:
                    XmlAttribute attribute = parentNode.SelectSingleNode(RelativeXPath) as XmlAttribute;
                    if (attribute == null)
                    {
                        throw new Exception(string.Format("No XMLAttribute found for relative path {0}",RelativeXPath));
                    }
                    OldAttValue = attribute.Value;
                    attribute.Value = Value;
                    break;
                case XPathXmlWebConfigModificationType.AddNode:
                    XmlNode node = parentNode.SelectSingleNode(RelativeXPath);
                    if (node != null)
                    {
                        LastOperationMessage = string.Format("XMLNode already exist for relative path {0}, ignoring it",  RelativeXPath);
                        return;
                    }
                    XmlNode newNode = null;
                    using (StringReader sr = new StringReader(Value))
                    {
                        using (XmlReader xmlReader = XmlReader.Create(sr))
                        {
                            newNode = doc.ReadNode(xmlReader);
                        }
                    }

                    switch (XPathInsertPosition)
                    {
                        case XPathXmlInsertPosition.Last:
                            parentNode.AppendChild(newNode);
                            break;
                        case XPathXmlInsertPosition.First:
                            parentNode.PrependChild(newNode);
                            break;
                        case XPathXmlInsertPosition.Before:
                            XmlNode childNodeBefore = parentNode.SelectSingleNode(RelativeSiblingXPath);
                            if (childNodeBefore == null)
                            {
                                throw new Exception(string.Format("No XmlNode found for RelativeXPathAfterBefore ({0})", RelativeSiblingXPath));
                            }
                            parentNode.InsertBefore(newNode, childNodeBefore);
                            break;
                        case XPathXmlInsertPosition.After:
                            XmlNode childNodeAfter = parentNode.SelectSingleNode(RelativeSiblingXPath);
                            if (childNodeAfter == null)
                            {
                                throw new Exception(string.Format("No XmlNode found for RelativeXPathAfterBefore ({0})", RelativeSiblingXPath));
                            }
                            parentNode.InsertAfter(newNode, childNodeAfter);
                            break;
                        default:
                            break;
                    }
                    break;
            }

        }

        public override void RemoveModification(System.Xml.XmlDocument doc, Microsoft.SharePoint.Administration.SPUrlZone zone, Microsoft.SharePoint.Administration.SPWebApplication webapp)
        {
            XmlNode parentNode = doc.SelectSingleNode(ParentXPath);

            if (parentNode == null)
            {
                throw new Exception(string.Format("Cannot find XMLNode for path {0}", ParentXPath));
            }
            switch (ModificationType)
            {
                case XPathXmlWebConfigModificationType.ModifyAttribute:
                    XmlAttribute attribute = parentNode.SelectSingleNode(RelativeXPath) as XmlAttribute;
                    if (attribute == null)
                    {
                        throw new Exception(string.Format("No XMLAttribute found for relative path {0}", RelativeXPath));
                    }
                    attribute.Value = OldAttValue;
                    break;
                case XPathXmlWebConfigModificationType.AddNode:
                    if (EraseNodeOnRemove)
                    {
                        XmlNode childNode = parentNode.SelectSingleNode(RelativeXPath);
                        if (childNode == null)
                        {
                            throw new Exception(string.Format("No XmlNode found for RelativeXPath ({0})", RelativeXPath));
                        }
                        parentNode.RemoveChild(childNode);
                    }
                    break;
            }
        }

        public override string FriendlyTypeName
        {
            get { return "XPath/Xml Modification"; }
        }

        public override bool SupportEditing
        {
            get { return true; }
        }

        public override string AdminPage
        {
            get { return "Winwise.SPWebConfig\\AddEditXPathXmlWebconfigModification.aspx"; }
        }


        public override void Validate()
        {
            base.Validate();

            if (string.IsNullOrEmpty(ParentXPath))
            {
                throw new Exception("ParentXPath should not be null or empty");
            }

            if (string.IsNullOrEmpty(RelativeXPath))
            {
                throw new Exception("RelativeXPath should not be null or empty");
            }


            if ((this.XPathInsertPosition == XPathXmlInsertPosition.After || this.XPathInsertPosition == XPathXmlInsertPosition.Before) &&
                string.IsNullOrEmpty(this.RelativeSiblingXPath))
            {
                throw new Exception(string.Format("When using {0}, you should fill the property RelativeXPathAfterBefore", XPathInsertPosition.ToString()));
            }

            if (this.ModificationType == XPathXmlWebConfigModificationType.AddNode)
            {
                try{
                 using (StringReader sr = new StringReader(Value))
                    {
                        using (XmlReader xmlReader = XmlReader.Create(sr))
                        {
                            new XmlDocument().ReadNode(xmlReader);
                        }
                    }
                }
                catch
                {
                    throw new Exception("When using ModificationType = XPathXmlWebConfigModificationType.AddNode, property Value should be a valid XmlNode");
                }
            }
        }
    }
}
